/* This is the code for a PIC16F88 to receive RF signals from a well-pump
   controller and display on an LCD.
   
   Data is received by debouncing a signal for the RF receiver at 1200baud.  Bits are sent using
   manchester encoding with a low-to-high transition to indicate a '0' and a high-to-low transition
   to indicate a '1'.
   Debouncing is done by sampling each bit 3 times and using the amount of time spent in the high vs
   low states to determine the value.  The RF protocol used is a slight modification of the SNAP
   protocol: http://www.hth.com/filelibrary/pdffiles/snap.pdf
   
   The LCD is driven via the USART, and is expected to be a 4 column screen with a SparkFun
   serial backpack: https://www.sparkfun.com/commerce/product_info.php?products_id=462
*/
#include <system.h>
#include "usart.h"
#include "crc16.h"

//#define SIMULATOR
//#define DEBUG
#pragma CLOCK_FREQ 8000000
#pragma DATA _CONFIG1, _LVP_OFF & _BODEN_ON & _MCLR_OFF & _WDT_OFF & _PWRTE_OFF & _INTRC_IO //0x3F54

#define SWAP( dst ) asm swapf _##dst, F
//flags
#define DATA_READY		0
#define START			1
#define LOCKED			2
#define INSYNC			3
#define READING			4
#define LO_NIBBLE		5
#define FOUND_HEADER	6
#define MINUTE			7

//pump control flags (str[11])
#define T0READY			0
#define SAMPLEREADY		1
#define ADREADY			2
#define LOW_LEVEL		3
#define PUMP_RAN_DRY	4
#define IN_RECOVERY		5
#define PUMP_READY		6
#define PUMP_ON			7

//Data structure (offeset in bytes to data[])
#define DATA_ON_TIME		0
#define DATA_OFF_TIME		1
#define DATA_MAX_ON_TIME	2 
#define DATA_RUNDRY_TIME    3
#define DATA_DEPTH_HI		4
#define DATA_DEPTH_LO		5
#define DATA_TEMP_F			6
#define DATA_STATUS			7

//sound flags
#define SOUND_PLAY		0
#define SOUND_ON		1
#define SOUND_HI		2

char flags = 0, sound_flags = 1;
char mod_count, roll = 0;
char rx;
char data[8];
char init[5] = {0xAA,0x99,0x66,0x55,0xA9};
//char nxt_tmr;
char tmr0_upper8 = 0;
char rxbuf;
char pos = 0;
char disp_count = 0;
char up_time, dn_time;
unsigned short today, ystday, avg;
unsigned short t1_overflow = 0, last_read = 0;
char min_count = 0;
char hist[42], hist_state=0;

char dbg1, dbg2=0;

#ifndef SIMULATOR
	#define PowerUpDisplay() {sendChar(0xFE); sendChar(0x0C);}
	#define PowerDownDisplay() { sendChar(0xFE); sendChar(0x08); }
	#define SetPosLine2() {sendChar(0xFE); sendChar(0x80|64);} //second line of display
	#define SetPosLine3() {sendChar(0xFE); sendChar(0x80|16);} //third line of display
	#define SetPosLine4() {sendChar(0xFE); sendChar(0x80|80);} //fourth line of display
	void ClearScreen()
	{
		sendChar(0xFE);
		sendChar(0x01);
		delay_ms(5);
	}
#else
	#define PowerUpDisplay() {sendChar('O'); sendChar('n'); sendChar('\n');}
	#define PowerDownDisplay() { sendChar('O'); sendChar('f'); sendChar('f'); sendChar('\n');}
	#define ClearScreen() {sendChar('C'); sendChar('l'); sendChar('e'); sendChar('a'); sendChar('r'); sendChar('\n');}
	#define SetPosLine2() {sendChar('\n');}
#endif

inline void stop_sound()
{
	clear_bit(sound_flags, SOUND_PLAY);		//Disable sound flags
	set_bit(portb, 1);					//Turn off port to reduce power
}

//Compute:
//  # of minutes pump ran today
//  # of minutes pump ran yesterday
//  Avg # of minutes pump ran per day for the past week
void ComputeHistory()
{
	unsigned short tmp;
	char day, i, base=0;
	avg=0;
	for (day = 0; day < 7; day++) {	//For each day
		tmp = 0;
		for (i = 0; i < 6; i++) {	//tmp = # of minutes ran this day
			tmp+=hist[base++];
		}
		if (day == 0)				//Save today's value
			today = tmp;
		else if (day == 1)			//Save yesterday's value
			ystday = tmp;
		avg += tmp;					//Sum total time for week
	}
	avg /= 7;						//Avg minutes per day
}

//History_4hr gets called once every four hours.
//It shifts 'hist' left one and recomputes the history buckets
void History_4hr()
{
	char i;
	for(i = 41; i > 0; i--) {
		hist[i] = hist[i-1];
	}
	hist[0]=0;
	ComputeHistory();
}

//Each time the pump turns off, add the number of minutes ran to the current 4hr bucket
void UpdateHistory()
{
	unsigned int avg;
	if (test_bit(data[DATA_STATUS], PUMP_ON)) {
		hist_state = 1;
	} else {
		if(hist_state) {		//the pump switched from on to off
			hist[0]+=data[DATA_ON_TIME];	//# of minutes pump ran
			hist_state=0;
			ComputeHistory();
		}
	}
}

void show_status(char read)
{
/*
	on_time=data[0];
	off_time=data[1];
	max_ontime=data[2];
	rundry_time=data[3];
	MAKESHORT(depth, data[5], data[4]);
	temperature=data[6];
	rx_flags=data[7];
*/
	unsigned char recover;
	unsigned short depth;
	depth=data[DATA_DEPTH_HI]<<8;
	depth|= data[DATA_DEPTH_LO];
	if (read) {
		UpdateHistory();
		disp_count++;
		last_read=0;
		if(test_bit(data[DATA_STATUS], LOW_LEVEL)) {		// Play sound on low-level
			set_bit(sound_flags, SOUND_PLAY);
		} else {
			stop_sound();
		}
	}
	ClearScreen();
	if(test_bit(data[DATA_STATUS], PUMP_RAN_DRY)) {
		sendChar('D');
		sendChar('r');
		sendChar('y');
		recover = data[DATA_RUNDRY_TIME] - data[DATA_OFF_TIME];
	} else {
		recover = 0;
		sendChar('O');
		if(test_bit(data[DATA_STATUS], PUMP_ON)) {
			sendChar('n');
		} else {
			sendChar('f');
			sendChar('f');
			if (test_bit(data[DATA_STATUS], IN_RECOVERY)) {
				recover = 60-data[DATA_MAX_ON_TIME];
				recover -= data[DATA_OFF_TIME];
			}
		}
	}
	sendChar(' ');
	sendDecimalShortPad(depth, 3);
	sendChar('g');
	sendChar('a');
	sendChar('l');
	sendChar(' ');
	sendChar(' ');
	sendDecimalByte(data[DATA_TEMP_F]);
	sendChar(0xdf);

	SetPosLine2();
	sendChar('R');
	sendChar('a');
	sendChar('n');
	sendDecimalByte(data[DATA_ON_TIME]);
	sendChar('/');
	sendDecimalByte(data[DATA_MAX_ON_TIME]);
	sendChar(' ');
	sendChar('R');
	sendChar('c');
	sendChar('v');
	sendChar('r');
	sendDecimalByte(recover);

	SetPosLine3();
	sendChar('T');
	sendChar('d');
	sendChar('y');
	sendChar(' ');
	sendChar('Y');
	sendChar('d');
	sendChar('y');
	sendChar(' ');
	sendChar('A');
	sendChar('v');
	sendChar('g');
	sendChar(' ');
	if(! read) {
		sendChar('S');
		sendChar('y');
		sendChar('n');
		sendChar('c');
	}
	
	SetPosLine4();
	sendDecimalShortPad(today, 2);
	sendChar(' ');
	sendDecimalShortPad(ystday, 2);
	sendChar(' ');
	sendDecimalShortPad(avg, 2);
	sendChar(' ');
	if(! read) {
		sendDecimalShortPad(last_read, 0);
	} else {
		char c;
		switch(disp_count & 0x03) { //Japaneese module doesn't have full ascii
			case 0: c = '^'; break;
			case 1: c = '>'; break;
			case 2: c = 'v'; break;
			case 3: c = '<'; break;
		}
		sendChar(c);
	}
}

/*
char check_crc()
{
	char i;
	ClearCrc();
	for(i=0;i<12; i++) {
		UpdateCrc(data[i]);
	}
	if (crc_hi == data[8] && crc_lo == data[9])
		return(1);
	return(0);
}
*/
void play_sound()
{
	if(! test_bit(sound_flags, SOUND_PLAY))		//No sound to play
		return;
	if(! test_bit(sound_flags, SOUND_HI)) {		//Only change flags when sound is off
	    char t1;
	    HIBYTE(t1, t1_overflow);
		if(test_bit(t1, 1) || (unsigned char)t1_overflow > 92)					//Only beep for 3 seconds per minute
			return;
		if(test_bit(t1_overflow, 4))			//Two-tone.  Alternate every 500ms
			set_bit(sound_flags, SOUND_ON);
		else
			clear_bit(sound_flags, SOUND_ON);
	}
	if(test_bit(sound_flags, SOUND_ON)) {		//Play a note at ~3600Hz
		if(test_bit(sound_flags, SOUND_HI)) {
			set_bit(portb, 1);
			clear_bit(sound_flags, SOUND_HI);
		} else {
			clear_bit(portb, 1);
			set_bit(sound_flags, SOUND_HI);
		}
	}
}

#define SET_TMR_0 126 // 544 instr. cycles (+10 interrupt overhead) = 554 = 277usec (target is 277.8)
void interrupt()
{
	//roll.0 :
	//roll.1 : edge detect on PORTB(7:4)
	//roll.2 :
	//roll.3 :
	//roll.4 :
	//roll.5 :
	//roll.6 : TIMER0 overflow
	//roll.7 : Last RX value (RB4)
	if(test_bit(intcon, RBIF) || test_bit(intcon, TMR0IF))
	{
		roll &= 0xBC; //save roll (7,5:2); roll:7 is last Rx value; roll:6 is (temporarily) whether TMR0IF occurred
		if(test_bit(intcon, TMR0IF)) {//reset timer-0 on overflow
			tmr0=SET_TMR_0;
			set_bit(roll, 6);
		}
		if(test_bit(intcon, RBIF)) {
			set_bit(roll, 1);
			clear_bit(intcon, RBIF);
		}
		if(test_bit(flags, LOCKED)) {	//Receiving 'good' data
			//The following code is in assembly because we need to do an add
			//and then check for overflow.  This can't be done very efficiently in C
			//This does the following with some error detection:
			//    if(roll.7)
			//        up_time += tmr2
			//    else
			//        dn_time += tmr2
        	asm { //find time RX spends hi (up_time) vs lo (dn_time)
				movf	_tmr2, W		//TIMER2->W
				btfsc	_pir1, TMR2IF	//if TIMER2 overflow
				movlw	0xff			//    then W = 0xff
				btfss	_roll, 7		//if !roll.7
				goto	lo_bit			//    then goto lo_bit
				addwf	_up_time, F		//up_time += W
				movlw	0xff
				btfsc	_status, C		//if up_time overflowed
				movwf	_up_time		//    then up_time = 0xff
				goto	done
lo_bit:
				addwf	_dn_time, F		//dn_time += W
				movlw	0xff
				btfsc	_status, C		//if dn_time overflowed
				movwf	_dn_time		//    then dn_time = 0xff
done:
			}
			tmr2=0;
			clear_bit(pir1, TMR2IF);	//Clear TIMER2 overflow
			if(! test_bit(roll, 6)) {	//if TIMER0 overflow not set
				clear_bit(roll, 7);		//    then roll.7 = RB4 (Rx)
				if(test_bit(portb, 4))
					set_bit(roll, 7);
				return;
			}
		}
		clear_bit(roll, 7);				//roll.7 = RB4 (Rx)
		if(test_bit(portb, 4))
			set_bit(roll, 7);
#ifdef DEBUG
		dbg1<<=1;
		if(test_bit(portb, 0))
			set_bit(dbg1,0);
#endif
		clear_bit(intcon, TMR0IF);			//Clear TIMER0 overflow
		tmr0_upper8++;						//Make Timer0 16bits.
		play_sound();						//Play any sounds that are active

		if(!test_bit(flags, LOCKED)) {		//Haven't locked on yet
			if((roll & 0x8A) == 0x08) {		//Rx is low (START-BIT=0), no change in last tick,
											//    and a change in previous tick
				if(test_bit(roll, 7))		//roll.0 = roll.7 (current Rx value) 
					set_bit(roll, 0);
				set_bit(flags, START);		//initialize state machine
				set_bit(flags, LOCKED);
				mod_count=0x10;				//mod=0, count=0;
				clear_bit(portb, 3);		//RB3 is a debug output
				set_bit(intcon, RBIE);		//enable PORTB level-change interrupt
											//    for counting up/dn time
				tmr2=0;						//Reset TIMER2
				clear_bit(pir1, TMR2IF);	//Clear TIMER2 overflow
				stop_sound();				//Disable beeper
			}
		} else {								// We have (potentially) locked on
#ifdef DEBUG2
			if(dbg2)
				set_bit(portb, 1);
			else
				clear_bit(portb, 1);
			dbg2^=0x01;
#endif
			if(up_time || test_bit(roll, 7)) {
				set_bit(roll, 0);
			}

			if(test_bit(roll, 0)) {				//last value != this value
												//initiate change in next cycle
				if(! test_bit(roll, 2) && ! test_bit(roll, 4)) {
					set_bit(mod_count, 5);
					clear_bit(mod_count, 4);
				}
			// we only do the rise, because we will often mark a fall as a rise, but should
			// never mark a rise as a fall.
			// This assumes Manchester encoding (rise and fall both occur frequently)
/*			} else if(test_bit(roll, 2) && test_bit(roll, 4)) {
				set_bit(mod_count, 4);
				set_bit(mod_count, 5);
*/			}

			if(! (mod_count&0xF0)) {			//Determine Rx value every 3rd TIMER0 overflow
				if(test_bit(roll,0)) 			//RB3 is a debug output; records current value
					set_bit(portb, 3);
				else
					clear_bit(portb, 3);
#ifdef DEBUG
				if((dbg1&0x01) != (roll & 0x01)) {
					_sendChar('T');
					_sendHexByte(roll);
					_sendHexByte(dbg1);
					_sendHexByte(mod_count);
					_sendHexByte(up_time);
					_sendHexByte(dn_time);
					_sendHexByte(flags);
					//delay_s(1);
					goto reset_state;
				}
#endif
				if(!test_bit(flags, START)) {	//last data byte finished
					if( test_bit(roll,0))		//if Current bit !=0, we didn't find a start-bit
						goto reset_state;		//    reset state-machine
					set_bit(flags, START);		//otherwise reinitialize stte-machine for next byte
					mod_count = 0;
				} else {
					if(test_bit(mod_count, 3)) {	//We have read 10 bits (1 start + 8 data +1 stop)
						if(test_bit(flags, INSYNC) || rx==0xAA) {
													//Only mark data as valid if we are already
													//synchronized, or the 1st header byte was found
							if(test_bit(roll,0)) {	//And (stop_bit = 1) was found
								set_bit(flags, DATA_READY);
								rxbuf=rx;
								clear_bit(flags, START);	//Get ready for next byte
								set_bit(flags, INSYNC);
							}
						}
						if(test_bit(flags, START)) {	//didn't find a stop bit
							while(--mod_count && test_bit(rx,7-mod_count))	//note that mod==0 if we get here!
								;						//look for 1st potential start bit (Rx = 0)
							if(! mod_count)  			//no potential start bits found
								goto reset_state;		//    so clear state-machine
						}
					}
					if (! test_bit(mod_count, 3)) { //count != 8 so just add this byte to the queue
						rx>>=1;
						if(test_bit(roll, 0))
							set_bit(rx, 7);
						mod_count++;
					}
				}
			}
			mod_count+=0x10;						//(mod++) % 3
			if(test_bit(mod_count,4) && test_bit(mod_count,5))
				mod_count &= 0x0f;
		} // flags & LOCKED
		up_time=0;
		dn_time=0;
		asm {  //roll <<= 2 and preserve roll:7 (no need to reset roll 1:0 as it will happen at the start of the next intr
			rlf		_roll, F
			rlf		_roll, F
			bcf		_roll, 7
			btfsc	_roll, 0
			bsf		_roll, 7
		}
		return;
reset_state:
		clear_bit(intcon, RBIE);
		clear_bit(flags, START);
		clear_bit(flags, LOCKED);
		clear_bit(flags, INSYNC);
		clear_bit(flags, FOUND_HEADER);
		return;
	}
	// timer1 overflow
	if (1831 == ++t1_overflow) {				//60 seconds have passed
		t1_overflow=0;
		set_bit(flags, MINUTE);					//mark another minute has passed
	}
	clear_bit(pir1, TMR1IF);
}

void main()
{
	char i;
	osccon=0x70;
	ansel=0x09;
	trisb=0xf1;
	portb=0xff;
//	nxt_tmr=SET_TMR_0;
	roll=0;
	spbrg=12; //9600bps
	for (i = 0; i < 42; i++)
		hist[i]=0;
	ComputeHistory();
	set_bit(rcsta, SPEN); //enable serial port
	set_bit(txsta, TXEN); //enable transmitter
	option_reg=0x01; //1:4 prescaler tmr0
	set_bit(t2con, TMR2ON);
	set_bit(t2con, 0); //1:4 prescaler
	tmr1h = 0;
	tmr1l = 0;
	t1con=0x05; //1:1 prescaler, internal clock, running
	
	tmr0=0;
	set_bit(pie1, TMR1IE); 
	set_bit(intcon, TMR0IE);
	set_bit(intcon, PEIE);
	set_bit(intcon, GIE); //enable interrupts
	while(1) {
		if(test_bit(flags, DATA_READY)) {
#ifndef DEBUG1
			if(! test_bit(flags, FOUND_HEADER)) {
				if(rxbuf==init[pos]) {
					pos++;
					if(pos==5) {
						set_bit(flags, FOUND_HEADER);
						set_bit(flags, READING);
						set_bit(flags, LO_NIBBLE);
					}
				} else {
					pos=0;
					if(rxbuf==init[pos])
						pos++;
				}
			} else {
				char buf, buf1,i;
				buf=rxbuf;
				for(i=0;i<4;i++) {
					buf1>>=1;
					if((buf&0x03) == 0x01)
						set_bit(buf1, 3);
					else if((buf&0x03) != 0x02) {
						clear_bit(flags, FOUND_HEADER);
						goto error;
					}
					buf>>=2;
				}
				if(test_bit(flags, LO_NIBBLE)) {
					data[pos-5]=buf1;
					clear_bit(flags, LO_NIBBLE);
				} else {
					data[pos-5]|=(buf1<<4);
					set_bit(flags, LO_NIBBLE);
					pos++;
					if(pos==13) {
						show_status(1);
						clear_bit(flags, FOUND_HEADER);
						clear_bit(flags, READING);
						pos=0;
					}
				}
			}
#else
			sendHexByte(rxbuf);
			sendChar(' ');
			pos=1;
#endif
error:
			clear_bit(flags, DATA_READY);
			tmr0_upper8=1;
		}
		if(!tmr0_upper8 || (test_bit(flags, READING) && ! test_bit(flags, FOUND_HEADER))) {
#ifdef DEBUG1
			if(pos) {
				sendChar('\n');
			}
#endif
			clear_bit(flags, FOUND_HEADER);
			clear_bit(flags, READING);
			pos=0;
		}
		if (test_bit(flags, MINUTE)) {
			clear_bit(flags, MINUTE);
			last_read++;
			if (240 == ++min_count) {
				min_count=0;
				History_4hr();
			}
			if(last_read > 1) {
				if(last_read == 10)			//Play sound after 10 minutes without signal
					set_bit(sound_flags, SOUND_PLAY);
				show_status(0);
			} else {
				stop_sound();
			}
		}
	}
}
